﻿using Common.EF.Structs;
using Common.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using Npgsql;
using NpgsqlTypes;
using Oracle.ManagedDataAccess.Client;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;

namespace Common.EF
{
    public class CommonContextPool : ICommonContextPool, IDisposable
    {
        private static readonly Dictionary<string, string> ENTITYIDS = new Dictionary<string, string>();
        private readonly ICommonContextPoolParam contextPoolParam;
        private readonly IServiceProvider provider;
        private readonly SortedDictionary<string, ICommonContext> commonContexts = new SortedDictionary<string, ICommonContext>();

        public CommonContextPool(IServiceProvider provider, ICommonContextPoolParam contextParam)
        {
            LibCheckNullUtils.CheckNull(contextParam, "contextPoolParam");
            LibCheckNullUtils.CheckNull(contextParam.Contexts, "contextPoolParam.Contxts");
            if (!contextParam.Contexts.Any())
            {
                throw new CommonException("Not contexts");
            }

            if (contextParam.Contexts.Select(t => t.Code).Distinct().Count() != contextParam.Contexts.Count)
            {
                throw new CommonException("cds");
            }

            if (!contextParam.Contexts.Any(t => t.ContextType == ContextTypeEnum.DefaultContext))
            {
                CommonEFUtils.ThrowByNoDefaultContext(this.Provider);
            }

            this.contextPoolParam = contextParam;
            this.provider = provider;
        }

        public SortedDictionary<string, ICommonContext> CommonContexts
        {
            get { return commonContexts; }
        }

        public ICommonContextPoolParam ContextPoolParam
        {
            get { return contextPoolParam; }
        }

        public IServiceProvider Provider
        {
            get { return provider; }
        }

        public async Task<T> GetEntityAsync<T>(string id, string[] includes)
            where T : EntityBase, new()
        {
            LibCheckNullUtils.CheckNull(id, "Id");
            var code = this.GetContextCodeById(id);
            var context = this.GetCommonContext(code);
            var ret = await context.GetEntityAsync<T>(t => t.id == id, includes);
            return ret;
        }

        public async Task<T> GetEntityAsync<T>(DateTime? beginTime, DateTime? endTime, Expression<Func<T, bool>> predicate, Dictionary<string, int> orders, string[] contextCodes, string[] includes)
            where T : EntityBase, new()
        {
            var ret = await this.GetEntitiesAsync<T>(beginTime, endTime, predicate, contextCodes, includes);
            if (ret != null && ret.Any())
            {
                var queryTable = ret.AsQueryable();
                if (orders != null && orders.Any())
                {
                    int type = 0;
                    foreach (var item in orders)
                    {
                        queryTable = queryTable.SetQueryableOrder(type, item.Key, item.Value);
                        type = 1;
                    }
                }

                return queryTable.FirstOrDefault();
            }

            return null;
        }

        public async Task<List<T>> GetEntitiesAsync<T>(List<string> ids, string[] includes)
            where T : EntityBase, new()
        {
            if (ids == null || !ids.Any())
            {
                return new List<T>();
            }

            ids = ids.Where(t => !string.IsNullOrWhiteSpace(t)).Distinct().ToList();
            if (!ids.Any())
            {
                return new List<T>();
            }

            string lastCode = string.Empty;
            List<string> lastIds = null;
            Dictionary<string, List<string>> data = new Dictionary<string, List<string>>();
            foreach (var id in ids)
            {
                var code = this.GetContextCodeById(id);
                if (!data.TryGetValue(code, out List<string> list))
                {
                    list = new List<string>();
                    data.Add(code, list);
                    lastIds = list;
                    lastCode = code;
                }

                list.Add(id);
            }

            if (data.Count == 1)
            {
                var context = GetCommonContext(lastCode);
                return await context.GetEntitiesAsync<T>(t => lastIds.Contains(t.id), includes);
            }

            List<T> ret = new List<T>();
            foreach (var r in data)
            {
                var context = GetCommonContext(r.Key);
                var tempids = r.Value;
                var tempResult = await context.GetEntitiesAsync<T>(t => tempids.Contains(t.id), includes);
                if (tempResult != null && tempResult.Any())
                {
                    ret.AddRange(tempResult);
                }
            }

            return ret;
        }

        public async Task<List<T>> GetEntitiesAsync<T>(DateTime? beginTime, DateTime? endTime, Expression<Func<T, bool>> predicate, string[] contextCodes, string[] includes)
            where T : EntityBase, new()
        {
            string entityName = typeof(T).Name;
            var list = contextCodes != null && contextCodes.Any() ? contextCodes.ToList() : DoGetReadContextCodeByEntityName(beginTime, endTime, entityName);
            if (list == null || !list.Any())
            {
                CommonEFUtils.ThrowByNoContext(this.Provider);
            }

            if (list.Count == 1)
            {
                var context = this.GetCommonContext(list[0]);
                var ret1 = await context.GetEntitiesAsync<T>(predicate, includes);
                return ret1;
            }

            var ret = await this.DoGetEntitiesAsync<T>(list, predicate, includes);
            return ret;
        }

        public async Task<List<T>> GetOrderEntitiesAsync<T>(DateTime? beginTime, DateTime? endTime, Expression<Func<T, bool>> predicate, Dictionary<string, int> orders, string[] contextCodes, string[] includes)
           where T : EntityBase, new()
        {
            if (orders == null || !orders.Any())
            {
                throw new CommonException("GetOrderEntities param error");
            }

            string entityName = typeof(T).Name;
            var list = contextCodes != null && contextCodes.Any() ? contextCodes.ToList() : DoGetReadContextCodeByEntityName(beginTime, endTime, entityName);
            if (list == null || !list.Any())
            {
                CommonEFUtils.ThrowByNoContext(this.Provider);
            }

            if (list.Count == 1)
            {
                var context = this.GetCommonContext(list[0]);
                var r1 = await context.GetOrderEntitiesAsync<T>(predicate, orders, includes);
                return r1;
            }

            var ret = await this.DoGetEntitiesAsync<T>(list, predicate, includes);
            var queryTable = ret.AsQueryable();
            int type = 0;
            foreach (var item in orders)
            {
                queryTable = queryTable.SetQueryableOrder(type, item.Key, item.Value);
                type = 1;
            }

            return queryTable.ToList();
        }

        public async Task<ApiPagedResult<T>> GetOrderPageEntitiesAsync<T>(DateTime? beginTime, DateTime? endTime, Expression<Func<T, bool>> predicate, Dictionary<string, int> orders, int startIndex, int pageSize, string[] contextCodes, string[] includes)
            where T : EntityBase, new()
        {
            if (orders == null || !orders.Any())
            {
                throw new CommonException("GetOrderPageEntities param error");
            }

            if (startIndex < 0 || pageSize < 1)
            {
                throw new ArgumentOutOfRangeException("GetOrderPageEntities page param error");
            }

            string entityName = typeof(T).Name;
            var list = contextCodes != null && contextCodes.Any() ? contextCodes.ToList() : DoGetReadContextCodeByEntityName(beginTime, endTime, entityName);
            if (list == null || !list.Any())
            {
                throw new CommonException("data Error");
            }

            if (list.Count == 1)
            {
                var context = this.GetCommonContext(list[0]);
                var r1 = await context.GetOrderPageEntitiesAsync<T>(predicate, orders, startIndex, pageSize, includes);
                return r1;
            }

            ApiPagedResult<T> result = new ApiPagedResult<T>();
            var ret = await this.DoGetOrderPageEntitiesAsync<T>(list, predicate, orders, startIndex, pageSize, includes);
            if (ret != null && ret.Data != null && ret.Data.Any())
            {
                var queryTable = ret.Data.AsQueryable();
                int type = 0;
                foreach (var item in orders)
                {
                    queryTable = queryTable.SetQueryableOrder(type, item.Key, item.Value);
                    type = 1;
                }

                result.TotalCount = ret.TotalCount;
                if (queryTable.Any())
                {
                    result.Data = queryTable.Skip(startIndex).Take(pageSize).ToList();
                }
            }

            if (result.Data == null)
            {
                result.Data = new List<T>();
            }

            return result;
        }

        public async Task<int> GetCountAsync<T>(DateTime? beginTime, DateTime? endTime, Expression<Func<T, bool>> predicate, string[] contextCodes)
          where T : EntityBase, new()
        {
            string entityName = typeof(T).Name;
            var list = contextCodes != null && contextCodes.Any() ? contextCodes.ToList() : DoGetReadContextCodeByEntityName(beginTime, endTime, entityName);
            if (list == null || !list.Any())
            {
                CommonEFUtils.ThrowByNoContext(this.Provider);
            }

            if (list.Count == 1)
            {
                var context = GetCommonContext(list[0]);
                return await context.GetCountAsync<T>(predicate);
            }

            int ret = 0;
            foreach (var item in list)
            {
                var context = GetCommonContext(item);
                var rx = await context.GetCountAsync<T>(predicate);
                ret += rx;
            }

            return ret;
        }

        public async Task<decimal> GetSumAsync<T>(DateTime? beginTime, DateTime? endTime, Expression<Func<T, bool>> predicate, Expression<Func<T, decimal>> sumExpression, string[] contextCodes)
            where T : EntityBase, new()
        {
            string entityName = typeof(T).Name;
            var list = contextCodes != null && contextCodes.Any() ? contextCodes.ToList() : DoGetReadContextCodeByEntityName(beginTime, endTime, entityName);
            if (list == null || !list.Any())
            {
                CommonEFUtils.ThrowByNoContext(this.Provider);
            }

            if (list.Count == 1)
            {
                var context = GetCommonContext(list[0]);
                return await context.GetSumAsync<T>(predicate, sumExpression);
            }

            decimal ret = 0;
            foreach (var item in list)
            {
                var context = GetCommonContext(item);
                var rx = await context.GetSumAsync<T>(predicate, sumExpression);
                ret += rx;
            }

            return ret;
        }

        public async Task<List<IGrouping<S, T>>> GetGroupByAsync<T, S>(DateTime? beginTime, DateTime? endTime, Expression<Func<T, bool>> predicate, Expression<Func<T, S>> groupByExpression, string[] contextCodes)
            where T : EntityBase, new()
        {
            string entityName = typeof(T).Name;
            var list = contextCodes != null && contextCodes.Any() ? contextCodes.ToList() : DoGetReadContextCodeByEntityName(beginTime, endTime, entityName);
            if (list == null || !list.Any())
            {
                CommonEFUtils.ThrowByNoContext(this.Provider);
            }

            if (list.Count == 1)
            {
                var context = GetCommonContext(list[0]);
                return await context.GetGroupByAsync<T, S>(predicate, groupByExpression);
            }

            List<IGrouping<S, T>> ret = new List<IGrouping<S, T>>();
            foreach (var item in list)
            {
                var context = GetCommonContext(item);
                var xx = await context.GetGroupByAsync<T, S>(predicate, groupByExpression);
                ret.AddRange(xx);
            }

            return ret;
        }

        public T GetEntity<T>(string id, string[] includes)
            where T : EntityBase, new()
        {
            LibCheckNullUtils.CheckNull(id, "Id");
            var code = this.GetContextCodeById(id);
            var context = this.GetCommonContext(code);
            var ret = context.GetEntity<T>(t => t.id == id, includes);
            return ret;
        }

        public T GetEntity<T>(DateTime? beginTime, DateTime? endTime, Expression<Func<T, bool>> predicate, Dictionary<string, int> orders, string[] contextCodes, string[] includes)
            where T : EntityBase, new()
        {
            var ret = this.GetEntities<T>(beginTime, endTime, predicate, contextCodes, includes);
            if (ret != null && ret.Any())
            {
                var queryTable = ret.AsQueryable();
                if (orders != null && orders.Any())
                {
                    int type = 0;
                    foreach (var item in orders)
                    {
                        queryTable = queryTable.SetQueryableOrder(type, item.Key, item.Value);
                        type = 1;
                    }
                }

                return queryTable.FirstOrDefault();
            }

            return null;
        }

        public List<T> GetEntities<T>(List<string> ids, string[] includes)
            where T : EntityBase, new()
        {
            if (ids == null || !ids.Any())
            {
                return new List<T>();
            }

            ids = ids.Where(t => !string.IsNullOrWhiteSpace(t)).Distinct().ToList();
            if (!ids.Any())
            {
                return new List<T>();
            }

            string lastCode = null;
            List<string> lastList = null;
            Dictionary<string, List<string>> data = new Dictionary<string, List<string>>();
            Dictionary<string, List<string>> tempData = new Dictionary<string, List<string>>();
            foreach (var id in ids)
            {
                var code = this.GetContextCodeById(id);
                if (!data.TryGetValue(code, out List<string> list))
                {
                    list = new List<string>();
                    data.Add(code, list);
                    lastCode = code;
                    lastList = list;
                }

                list.Add(id);
            }

            if (data.Count == 1)
            {
                var context = GetCommonContext(lastCode);
                return context.GetEntities<T>(t => lastList.Contains(t.id), includes);
            }

            List<T> ret = new List<T>();
            foreach (var item in data)
            {
                var context = GetCommonContext(item.Key);
                var tempIds = item.Value;
                var rx = context.GetEntities<T>(t => tempIds.Contains(t.id), includes);
                ret.AddRange(rx);
            }

            return ret;
        }

        public List<T> GetEntities<T>(DateTime? beginTime, DateTime? endTime, Expression<Func<T, bool>> predicate, string[] contextCodes, string[] includes)
            where T : EntityBase, new()
        {
            string entityName = typeof(T).Name;
            var list = contextCodes != null && contextCodes.Any() ? contextCodes.ToList() : DoGetReadContextCodeByEntityName(beginTime, endTime, entityName);
            if (list == null || !list.Any())
            {
                CommonEFUtils.ThrowByNoContext(this.Provider);
            }

            if (list.Count == 1)
            {
                var context = this.GetCommonContext(list[0]);
                var ret1 = context.GetEntities<T>(predicate, includes);
                return ret1;
            }

            var ret = this.DoGetEntitiesAsync<T>(list, predicate, includes).Result;
            return ret;
        }

        public List<T> GetOrderEntities<T>(DateTime? beginTime, DateTime? endTime, Expression<Func<T, bool>> predicate, Dictionary<string, int> orders, string[] contextCodes, string[] includes)
           where T : EntityBase, new()
        {
            if (orders == null || !orders.Any())
            {
                throw new CommonException("GetOrderEntities param error");
            }

            string entityName = typeof(T).Name;
            var list = contextCodes != null && contextCodes.Any() ? contextCodes.ToList() : DoGetReadContextCodeByEntityName(beginTime, endTime, entityName);
            if (list == null || !list.Any())
            {
                CommonEFUtils.ThrowByNoContext(this.Provider);
            }

            if (list.Count == 1)
            {
                var context = this.GetCommonContext(list[0]);
                var r1 = context.GetOrderEntities<T>(predicate, orders, includes);
                return r1;
            }

            var ret = this.DoGetEntitiesAsync<T>(list, predicate, includes).Result;
            var queryTable = ret.AsQueryable();
            int type = 0;
            foreach (var item in orders)
            {
                queryTable = queryTable.SetQueryableOrder(type, item.Key, item.Value);
                type = 1;
            }

            return queryTable.ToList();
        }

        public ApiPagedResult<T> GetOrderPageEntities<T>(DateTime? beginTime, DateTime? endTime, Expression<Func<T, bool>> predicate, Dictionary<string, int> orders, int startIndex, int pageSize, string[] contextCodes, string[] includes)
            where T : EntityBase, new()
        {
            if (orders == null || !orders.Any())
            {
                throw new CommonException("GetOrderPageEntities param error");
            }

            if (startIndex < 0 || pageSize < 1)
            {
                throw new ArgumentOutOfRangeException("GetOrderPageEntities page param error");
            }

            string entityName = typeof(T).Name;
            var list = contextCodes != null && contextCodes.Any() ? contextCodes.ToList() : DoGetReadContextCodeByEntityName(beginTime, endTime, entityName);
            if (list == null || !list.Any())
            {
                throw new CommonException("data Error");
            }

            if (list.Count == 1)
            {
                var context = this.GetCommonContext(list[0]);
                var r1 = context.GetOrderPageEntities<T>(predicate, orders, startIndex, pageSize, includes);
                return r1;
            }

            ApiPagedResult<T> result = new ApiPagedResult<T>();
            var ret = this.DoGetOrderPageEntitiesAsync<T>(list, predicate, orders, startIndex, pageSize, includes).Result;
            if (ret != null && ret.Data != null && ret.Data.Any())
            {
                var queryTable = ret.Data.AsQueryable();
                int type = 0;
                foreach (var item in orders)
                {
                    queryTable = queryTable.SetQueryableOrder(type, item.Key, item.Value);
                    type = 1;
                }

                result.TotalCount = ret.TotalCount;
                if (queryTable.Any())
                {
                    result.Data = queryTable.Skip(startIndex).Take(pageSize).ToList();
                }
            }

            if (result.Data == null)
            {
                result.Data = new List<T>();
            }

            return result;
        }

        public int GetCount<T>(DateTime? beginTime, DateTime? endTime, Expression<Func<T, bool>> predicate, string[] contextCodes)
          where T : EntityBase, new()
        {
            string entityName = typeof(T).Name;
            var list = contextCodes != null && contextCodes.Any() ? contextCodes.ToList() : DoGetReadContextCodeByEntityName(beginTime, endTime, entityName);
            if (list == null || !list.Any())
            {
                CommonEFUtils.ThrowByNoContext(this.Provider);
            }

            if (list.Count == 1)
            {
                var context = GetCommonContext(list[0]);
                return context.GetCount<T>(predicate);
            }

            int ret = 0;
            foreach (var item in list)
            {
                var context = GetCommonContext(item);
                var rx = context.GetCount<T>(predicate);
                ret += rx;
            }

            return ret;
        }

        public decimal GetSum<T>(DateTime? beginTime, DateTime? endTime, Expression<Func<T, bool>> predicate, Expression<Func<T, decimal>> sumExpression, string[] contextCodes) where T : EntityBase, new()
        {
            string entityName = typeof(T).Name;
            var list = contextCodes != null && contextCodes.Any() ? contextCodes.ToList() : DoGetReadContextCodeByEntityName(beginTime, endTime, entityName);
            if (list == null || !list.Any())
            {
                CommonEFUtils.ThrowByNoContext(this.Provider);
            }

            if (list.Count == 1)
            {
                var context = GetCommonContext(list[0]);
                return context.GetSum<T>(predicate, sumExpression);
            }

            decimal ret = 0;
            foreach (var item in list)
            {
                var context = GetCommonContext(item);
                var rx = context.GetSum<T>(predicate, sumExpression);
                ret += rx;
            }

            return ret;
        }

        public List<IGrouping<S, T>> GetGroupBy<T, S>(DateTime? beginTime, DateTime? endTime, Expression<Func<T, bool>> predicate, Expression<Func<T, S>> groupByExpression, string[] contextCodes)
            where T : EntityBase, new()
        {
            string entityName = typeof(T).Name;
            var list = contextCodes != null && contextCodes.Any() ? contextCodes.ToList() : DoGetReadContextCodeByEntityName(beginTime, endTime, entityName);
            if (list == null || !list.Any())
            {
                CommonEFUtils.ThrowByNoContext(this.Provider);
            }

            if (list.Count == 1)
            {
                var context = GetCommonContext(list[0]);
                return context.GetGroupBy<T, S>(predicate, groupByExpression);
            }

            List<IGrouping<S, T>> ret = new List<IGrouping<S, T>>();
            foreach (var item in list)
            {
                var context = GetCommonContext(item);
                var xx = context.GetGroupBy<T, S>(predicate, groupByExpression);
                ret.AddRange(xx);
            }

            return ret;
        }

        public List<string> GetReadContextCodeByEntityName(DateTime? beginTime, DateTime? endTime, string entityName)
        {
            return this.DoGetReadContextCodeByEntityName(beginTime, endTime, entityName);
        }

        public List<string> GetContextCodeByEntityName(string entityName)
        {
            return this.DoGetContextCodeByEntityName(entityName);
        }

        public void AddEntities<T>(List<T> entities)
           where T : EntityBase, new()
        {
            if (entities == null || !entities.Any())
            {
                return;
            }

            Dictionary<string, List<T>> data = new Dictionary<string, List<T>>();
            List<string> codes = new List<string>();
            foreach (var entity in entities)
            {
                var code = this.GetContextCodeById(entity.id);
                if (!codes.Contains(code))
                {
                    codes.Add(code);
                }

                if (!data.TryGetValue(code, out List<T> list))
                {
                    list = new List<T>();
                    data.Add(code, list);
                }

                list.Add(entity);
            }

            if (codes.Count == 1)
            {
                var context = this.GetCommonContext(codes[0]);
                context.AddEntities<T>(entities);
                return;
            }

            foreach (var item in data)
            {
                var context = this.GetCommonContext(item.Key);
                context.AddEntities<T>(item.Value);
            }
        }

        public void UpdateEntities<T>(List<T> entities, List<T> entitiesDb, params string[] updateProperies)
           where T : EntityBase, new()
        {
            if (entities == null || !entities.Any())
            {
                return;
            }

            Dictionary<string, List<T>> data = new Dictionary<string, List<T>>();
            List<string> codes = new List<string>();
            foreach (var entity in entities)
            {
                var code = this.GetContextCodeById(entity.id);
                if (!codes.Contains(code))
                {
                    codes.Add(code);
                }

                if (!data.TryGetValue(code, out List<T> list))
                {
                    list = new List<T>();
                    data.Add(code, list);
                }

                list.Add(entity);
            }

            if (codes.Count == 1)
            {
                var context = this.GetCommonContext(codes[0]);
                context.UpdateEntities<T>(entities, entitiesDb, updateProperies);
                return;
            }

            Dictionary<string, List<T>> tempDatas = new Dictionary<string, List<T>>();
            int index = 0;
            foreach (var item in data)
            {
                var context = this.GetCommonContext(item.Key);
                context.UpdateEntities<T>(item.Value, entitiesDb, updateProperies);
            }
        }

        public void DeleteEntity<T>(DateTime? beginTime, DateTime? endTime, Expression<Func<T, bool>> predicate, string[] contextCodes)
          where T : EntityBase, new()
        {
            string entityName = typeof(T).Name;
            var list = contextCodes != null && contextCodes.Any() ? contextCodes.ToList() : GetReadContextCodeByEntityName(beginTime, endTime, entityName);
            if (list == null || !list.Any())
            {
                CommonEFUtils.ThrowByNoContext(this.Provider);
            }

            if (list.Count == 1)
            {
                var context = this.GetCommonContext(list[0]);
                context.DeleteEntity<T>(predicate);
                return;
            } 

            foreach (var item in list)
            {
                var context = this.GetCommonContext(item);
                context.DeleteEntity<T>(predicate);
            }
        }

        public void DeleteEntityByCommit<T>(DateTime? beginTime, DateTime? endTime, Expression<Func<T, bool>> predicate, string[] contextCodes)
          where T : EntityBase, new()
        {
            string entityName = typeof(T).Name;
            var list = contextCodes != null && contextCodes.Any() ? contextCodes.ToList() : GetReadContextCodeByEntityName(beginTime, endTime, entityName);
            if (list == null || !list.Any())
            {
                CommonEFUtils.ThrowByNoContext(this.Provider);
            }

            if (list.Count == 1)
            {
                var context = this.GetCommonContext(list[0]);
                context.DeleteEntityByCommit<T>(predicate);
                return;
            }
             
            foreach (var item in list)
            {
                var context = this.GetCommonContext(item);
                context.DeleteEntityByCommit<T>(predicate);
            }
        }

        public void UpdateEntitiesByByCommit<T>(DateTime? beginTime, DateTime? endTime, Expression<Func<T, bool>> predicate, Expression<Func<T, T>> updateExpression, string[] contextCodes)
          where T : EntityBase, new()
        {
            string entityName = typeof(T).Name;
            var list = contextCodes != null && contextCodes.Any() ? contextCodes.ToList() : GetReadContextCodeByEntityName(beginTime, endTime, entityName);
            if (list == null || !list.Any())
            {
                CommonEFUtils.ThrowByNoContext(this.Provider);
            }

            if (list.Count == 1)
            {
                var context = this.GetCommonContext(list[0]);
                context.UpdateEntitiesByByCommit<T>(predicate, updateExpression);
                return;
            }
             
            foreach (var item in list)
            {
                var context = this.GetCommonContext(item);
                context.UpdateEntitiesByByCommit<T>(predicate, updateExpression);
            }
        }

        public void DeleteEntity<T>(List<string> ids) where T : EntityBase, new()
        {
            if (ids == null || !ids.Any())
            {
                return;
            } 

            ids = ids.Where(t => !string.IsNullOrWhiteSpace(t)).Distinct().ToList();
            if (!ids.Any())
            {
                return;
            }

            Dictionary<string, List<string>> data = new Dictionary<string, List<string>>();
            string lastCode = string.Empty;
            foreach (var id in ids)
            {
                var code = this.GetContextCodeById(id);
                lastCode = code;
                if (!data.TryGetValue(code, out List<string> list))
                {
                    list = new List<string>();
                    data.Add(code, list);
                }

                list.Add(id);
            }

            if (data.Count == 1)
            {
                var context = this.GetCommonContext(lastCode);
                context.DeleteEntity<T>(t => ids.Contains(t.id));
                return;
            }

            foreach (var item in data)
            {
                var context = this.GetCommonContext(item.Key);
                var tempIds = item.Value;
                context.DeleteEntity<T>(t => tempIds.Contains(t.id));
            }
        }

        public async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
        {
            int ret = 0;
            foreach (var t in CommonContexts)
            {
                if (t.Value == null)
                {
                    continue;
                }

                var xxt = await t.Value.SaveChangesAsync(cancellationToken);
                ret += xxt;
            }

            return ret;
        }

        public int SaveChanges()
        {
            if (this.CommonContexts.Count == 1)
            {
                var context = this.CommonContexts.FirstOrDefault();
                return context.Value.SaveChanges();
            }

            return this.SaveChangesAsync().Result;
        }

        public async Task DatabaseMigrateAsync(DateTime? beginTime, DateTime? endTime)
        {
            List<string> activeCodes = null;
            if (this.ContextPoolParam.Contexts != null)
            {
                activeCodes = this.GetReadContextCodeByList(this.ContextPoolParam.Contexts, beginTime, endTime);
            }

            if (activeCodes == null || !activeCodes.Any())
            {
                return;
            }

            ICommonMigrateContext context = null;
            try
            {
                if (this.provider != null)
                {
                    context = provider.GetService<ICommonMigrateContext>();
                }
            }
            catch
            {
            }

            if (context == null)
            {
                throw new CommonException("ICommonMigrateContext null");
            }

            foreach (var item in activeCodes)
            {
                var p = this.ContextPoolParam.Contexts.FirstOrDefault(t => t.Code == item);
                if (p == null)
                {
                    continue;
                }

                await context.DatabaseMigrateAsync(this.provider, p);
            }
        }

        public string GetContextCodeById(string id)
        {
            if (string.IsNullOrEmpty(id))
            {
                return string.Empty;
            }

            var temp = id.Split('_');
            if (temp.Length < 2)
            {
                CommonEFUtils.ThrowByIdError(this.Provider, id);
            }

            return temp[0];
        }

        public virtual void Dispose()
        {
            foreach (var item in CommonContexts)
            {
                try
                {
                    item.Value.Dispose();
                }
                catch 
                { 
                }
            }

            CommonContexts.Clear();
        }

        public void ExecuteReaderByContext(DateTime? beginTime, DateTime? endTime, CommandType commandType, string sql, ApiDbParameter[] parameters, Action<DbDataReader> readerAction, string[] contextCodes)
        {
            var list = contextCodes != null && contextCodes.Any() ? contextCodes.ToList() :
                this.GetReadContextCodeByList(this.ContextPoolParam.Contexts, beginTime, endTime);
            if (!list.Any())
            {
                return;
            }

            var ret = this.ContextPoolParam.Contexts.Where(t => list.Contains(t.Code)).ToList();
            if (!ret.Any())
            {
                return;
            }

            if (ret.Count == 1)
            {
                var c = ret[0];
                this.ExecuteReaderByConn(c.DBType, c.ConnString, commandType, sql, parameters, readerAction);
                return;
            }

            foreach (var item in ret)
            {
                var c = item;
                this.ExecuteReaderByConn(c.DBType, c.ConnString, commandType, sql, parameters, readerAction);
            }
        }

        public List<string> GetEntityIds(IDParam model)
        {
            LibCheckNullUtils.CheckNull(model.EntityName, "model.EntityName");
            if (model.Count == 0)
            {
                throw new CommonException("param erorr");
            }

            if (!string.IsNullOrEmpty(model.ParentId))
            {
                var c = GetContextCodeById(model.ParentId);
                List<string> ret1 = new List<string>();
                for (var i = 0; i < model.Count; i++)
                {
                    ret1.Add(GetIdByContextCode(c));
                }

                return ret1;
            }

            List<string> ret = new List<string>();
            var codes = this.GetContextCodeByEntityName(model.EntityName);
            List<string> realCodes = new List<string>();
            string curr = string.Empty;

            try
            {
                ENTITYIDS.TryGetValue(model.EntityName, out curr);
            }
            catch
            {
            }

            bool isGet = string.IsNullOrEmpty(curr) || !codes.Contains(curr);
            for (int i = 0; i < 2; i++)
            {
                foreach (var c in codes)
                {
                    if (isGet)
                    {
                        if (model.IsOnlyOneAccess)
                        {
                            if (!realCodes.Any())
                            {
                                realCodes.Add(c);
                            }
                        }
                        else if (!realCodes.Contains(c))
                        {
                            realCodes.Add(c);
                        }
                    }
                    else if (!string.IsNullOrEmpty(curr))
                    {
                        if (c == curr)
                        {
                            isGet = true;
                        }
                    }
                }
            }

            string r = null;
            int j = 0;
            for (int i = 0; i < model.Count; i++)
            {
                if (j >= realCodes.Count)
                {
                    j = 0;
                }

                r = realCodes[j];
                ret.Add(GetIdByContextCode(r));
                j++;
            }

            try
            {
                ENTITYIDS[model.EntityName] = r;
            }
            catch
            {
            }

            return ret.Count != model.Count ? throw new Exception("id count no match") : ret;
        }

        public void ExecuteNonQueryByContext(DateTime? beginTime, DateTime? endTime, string[] contextCodes, CommandType commandType, string sql, ApiDbParameter[] parameters)
        {
            var list = contextCodes != null && contextCodes.Any() ? contextCodes.ToList() :
                this.GetReadContextCodeByList(this.ContextPoolParam.Contexts, beginTime, endTime);
            if (!list.Any())
            {
                return;
            }

            var ret = this.ContextPoolParam.Contexts.Where(t => list.Contains(t.Code)).ToList();
            if (!ret.Any())
            {
                return;
            }

            if (ret.Count == 1)
            {
                var c = ret[0];
                this.ExecuteNonQueryByConn(c.DBType, c.ConnString, commandType, sql, parameters);
                return;
            }

            foreach (var item in ret)
            {
                var c = item;
                this.ExecuteNonQueryByConn(c.DBType, c.ConnString, commandType, sql, parameters);
            }
        }

        /// <summary>
        /// 连接执行 注意：本方法未释放连接
        /// </summary>
        /// <param name="dataBaseType"></param>
        /// <param name="conn"></param>
        /// <param name="commandType"></param>
        /// <param name="sql"></param>
        /// <param name="parameters"></param>
        /// <param name="readerAction"></param>
        /// <param name="close"></param>
        private void ExecuteReaderByConn(DataBaseTypeEnum dataBaseType, DbConnection conn, CommandType commandType, string sql, ApiDbParameter[] parameters, Action<DbDataReader> readerAction)
        {
            if (string.IsNullOrEmpty(sql))
            {
                return;
            }

            if (conn != null && conn.State == ConnectionState.Closed)
            {
                conn.Open();
            }

            if (conn != null)
            {
                var cmd = GetDbCommand(dataBaseType, conn, sql, parameters);
                if (cmd != null)
                {
                    cmd.CommandType = commandType;
                    using (var reader = cmd.ExecuteReader())
                    {
                        readerAction?.Invoke(reader);
                    }
                }
            }
        }

        private void ExecuteReaderByConn(DataBaseTypeEnum dataBaseType, string connString, CommandType commandType, string sql, ApiDbParameter[] parameters, Action<DbDataReader> readerAction)
        {
            var conn = GetDbConnection(dataBaseType, connString);
            try
            {
                ExecuteReaderByConn(dataBaseType, conn, commandType, sql, parameters, readerAction);
            }
            finally
            {
                if (conn != null)
                {
                    conn.Dispose();
                }
            }
        }

        private string GetIdByContextCode(string code)
        {
            return string.IsNullOrEmpty(code) ? throw new CommonException("code is null") : code + "_" + LibSysUtils.GetSequentialGuidId();
        }

        /// <summary>
        /// 连接执行 注意：本方法未释放连接
        /// </summary>
        /// <param name="dataBaseType"></param>
        /// <param name="conn"></param>
        /// <param name="commandType"></param>
        /// <param name="sql"></param>
        /// <param name="parameters"></param>
        /// <param name="readerAction"></param>
        /// <param name="close"></param>
        private void ExecuteNonQueryByConn(DataBaseTypeEnum dataBaseType, DbConnection conn, CommandType commandType, string sql, ApiDbParameter[] parameters)
        {
            if (string.IsNullOrEmpty(sql))
            {
                return;
            }

            if (conn != null && conn.State == ConnectionState.Closed)
            {
                conn.Open();
            }

            if (conn != null)
            {
                var cmd = GetDbCommand(dataBaseType, conn, sql, parameters);
                if (cmd != null)
                {
                    cmd.CommandType = commandType;
                    cmd.ExecuteNonQuery();
                }
            }
        }

        private void ExecuteNonQueryByConn(DataBaseTypeEnum dataBaseType, string connString, CommandType commandType, string sql, ApiDbParameter[] parameters)
        {
            var conn = GetDbConnection(dataBaseType, connString);
            try
            {
                ExecuteNonQueryByConn(dataBaseType, conn, commandType, sql, parameters);
            }
            finally
            {
                if (conn != null)
                {
                    conn.Dispose();
                }
            }
        }

        private DbConnection GetDbConnection(DataBaseTypeEnum dataBaseType, string connString)
        {
            switch (dataBaseType)
            {
                case DataBaseTypeEnum.PostgreSQL:
                    return new NpgsqlConnection(connString);
                case DataBaseTypeEnum.Oracle:
                    return new OracleConnection(connString);
                default:
                    throw new Exception("un do");
            }
        }

        private DbParameter[] GetDbParameterByApiParameter(DataBaseTypeEnum dataBaseType, ApiDbParameter[] parameters)
        {
            if (parameters == null || !parameters.Any())
            {
                return null;
            }

            List<DbParameter> list = new List<DbParameter>();
            if (dataBaseType == DataBaseTypeEnum.PostgreSQL)
            {
                foreach (var p in parameters)
                {
                    LibCheckNullUtils.CheckNull(p.ParamName, "p.FieldName");
                    if (p.ParamType == ApiDbParameterParamTypeEnum.String)
                    {
                        if (string.Compare(p.ParamValue, "false", true) == 0 || string.Compare(p.ParamValue, "true", true) == 0)
                        {
                            var val = LibSysUtils.ToBoolean(p.ParamValue);
                            list.Add(new NpgsqlParameter("@" + p.ParamName, NpgsqlDbType.Boolean)
                            {
                                Value = val
                            });
                        }
                        else
                        {
                            list.Add(new NpgsqlParameter("@" + p.ParamName, NpgsqlDbType.Varchar, p.ParamLength == 0 ? 800 : p.ParamLength)
                            {
                                Value = p.ParamValue
                            });
                        }
                    }
                    else if (p.ParamType == ApiDbParameterParamTypeEnum.DateTime)
                    {
                        var val = LibSysUtils.ToDateTime(p.ParamValue);
                        if (val == null && !string.IsNullOrEmpty(p.ParamValue))
                        {
                            throw new Exception("convert " + p.ParamValue + " time is error");
                        }

                        list.Add(new NpgsqlParameter("@" + p.ParamName, NpgsqlDbType.Timestamp)
                        {
                            Value = val
                        });
                    }
                    else if (p.ParamType == ApiDbParameterParamTypeEnum.Int)
                    {
                        var val = LibSysUtils.ToInt(p.ParamValue);
                        list.Add(new NpgsqlParameter("@" + p.ParamName, NpgsqlDbType.Integer)
                        {
                            Value = val
                        });
                    }
                    else if (p.ParamType == ApiDbParameterParamTypeEnum.Boolean)
                    {
                        var val = LibSysUtils.ToBoolean(p.ParamValue);
                        list.Add(new NpgsqlParameter("@" + p.ParamName, NpgsqlDbType.Boolean)
                        {
                            Value = val
                        });
                    }
                    else if (p.ParamType == ApiDbParameterParamTypeEnum.Decimal)
                    {
                        var val = LibSysUtils.ToDecimal(p.ParamValue);
                        list.Add(new NpgsqlParameter("@" + p.ParamName, NpgsqlDbType.Numeric)
                        {
                            Value = val
                        });
                    }
                }
            }
            else if (dataBaseType == DataBaseTypeEnum.Oracle)
            {
                foreach (var p in parameters)
                {
                    LibCheckNullUtils.CheckNull(p.ParamName, "p.FieldName");

                    if (p.ParamType == ApiDbParameterParamTypeEnum.String)
                    {
                        if (string.Compare(p.ParamValue, "false", true) == 0 || string.Compare(p.ParamValue, "true", true) == 0)
                        {
                            var val = LibSysUtils.ToBoolean(p.ParamValue);
                            list.Add(new OracleParameter(":" + p.ParamName, OracleDbType.Boolean)
                            {
                                Value = val
                            });
                        }
                        else
                        {
                            list.Add(new OracleParameter(":" + p.ParamName, OracleDbType.Varchar2, p.ParamLength == 0 ? 800 : p.ParamLength)
                            {
                                Value = p.ParamValue
                            });
                        }
                    }
                    else if (p.ParamType == ApiDbParameterParamTypeEnum.DateTime)
                    {
                        var val = LibSysUtils.ToDateTime(p.ParamValue);
                        if (val == null && !string.IsNullOrEmpty(p.ParamValue))
                        {
                            throw new Exception("convert " + p.ParamValue + " time is error");
                        }

                        list.Add(new OracleParameter(":" + p.ParamName, OracleDbType.TimeStamp)
                        {
                            Value = val
                        });
                    }
                    else if (p.ParamType == ApiDbParameterParamTypeEnum.Int)
                    {
                        var val = LibSysUtils.ToInt(p.ParamValue);
                        list.Add(new OracleParameter(":" + p.ParamName, OracleDbType.Int32)
                        {
                            Value = val
                        });
                    }
                    else if (p.ParamType == ApiDbParameterParamTypeEnum.Boolean)
                    {
                        var val = LibSysUtils.ToBoolean(p.ParamValue);
                        list.Add(new OracleParameter(":" + p.ParamName, OracleDbType.Boolean)
                        {
                            Value = val
                        });
                    }
                    else if (p.ParamType == ApiDbParameterParamTypeEnum.Decimal)
                    {
                        var val = LibSysUtils.ToDecimal(p.ParamValue);
                        list.Add(new OracleParameter(":" + p.ParamName, OracleDbType.Decimal)
                        {
                            Value = val
                        });
                    }
                }
            }

            return list.ToArray();
        }

        private DbCommand GetDbCommand(DataBaseTypeEnum dataBaseType, DbConnection conn, string sql, ApiDbParameter[] parameters)
        {
            if (dataBaseType == DataBaseTypeEnum.Oracle)
            {
                sql = sql.Replace("@", ":");
            }

            DbCommand command = null;
            switch (dataBaseType)
            {
                case DataBaseTypeEnum.PostgreSQL:
                    command = new NpgsqlCommand(sql, conn as NpgsqlConnection);
                    break;
                case DataBaseTypeEnum.Oracle:
                    command = new OracleCommand(sql, conn as OracleConnection);
                    break;
            }

            if (command == null)
            {
                throw new Exception("un do");
            }

            if (parameters != null && parameters.Length > 0)
            {
                var ps = GetDbParameterByApiParameter(dataBaseType, parameters);
                foreach (var item in ps)
                {
                    if (item.Value == null)
                    {
                        item.Value = DBNull.Value;
                    }

                    command.Parameters.Add(item);
                }
            }

            return command;
        }

        private ICommonContext GetCommonContext(string code)
        {
            if (!this.CommonContexts.TryGetValue(code, out ICommonContext ret))
            {
                CommonContextParam context = null;
                foreach (var p in this.ContextPoolParam.Contexts)
                {
                    if (p.Code == code)
                    {
                        context = p;
                        break;
                    }
                }

                ret = CreateCommonContext(context);
                CommonEFUtils.CheckCommonContext(this.Provider, ret, code);
                CommonContexts[context.Code] = ret;
            }

            CommonEFUtils.CheckCommonContext(this.Provider, ret, code);
            return ret;
        }

        private ICommonContext CreateCommonContext(CommonContextParam contextParam)
        {
            return new CommonContext(Provider, contextParam);
        }

        private async Task<List<T>> DoGetEntitiesAsync<T>(List<string> codes, Expression<Func<T, bool>> predicate, string[] includes) where T : EntityBase, new()
        {
            Dictionary<string, bool> datas = new Dictionary<string, bool>();
            if (codes.Count == 1)
            {
                var context = this.GetCommonContext(codes[0]);
                var ret1 = await context.GetEntitiesAsync<T>(predicate, includes);
                return ret1;
            }

            List<T> ret = new List<T>();
            foreach (var item in codes)
            {
                var context = this.GetCommonContext(item);
                var ret1 = await context.GetEntitiesAsync<T>(predicate, includes);
                ret.AddRange(ret1);
            }

            return ret;
        }

        private async Task<ApiPagedResult<T>> DoGetOrderPageEntitiesAsync<T>(List<string> codes, Expression<Func<T, bool>> predicate, Dictionary<string, int> orders, int startIndex, int pageSize, string[] includes) where T : EntityBase, new()
        {
            var list = new List<T>();
            ApiPagedResult<T> ret = new ApiPagedResult<T>
            {
                Data = list
            };
            foreach (var item in codes)
            {
                var context = this.GetCommonContext(item);
                var rx = await context.GetOrderPageEntitiesAsync<T>(predicate, orders, startIndex, pageSize, includes);
                if (rx != null && rx.Data != null && rx.Data.Any())
                {
                    list.AddRange(rx.Data);
                    ret.TotalCount += rx.TotalCount;
                }
            }

            return ret;
        }

        private List<string> GetReadContextCodeByList(List<CommonContextParam> list, DateTime? beginTime, DateTime? endTime)
        {
            if (list == null || !list.Any())
            {
                return new List<string>();
            }

            if (beginTime == null && endTime == null)
            {
                throw new CommonException("param error");
            }

            return list.Where(p => 
                    p.ContextType == ContextTypeEnum.DefaultContext ||
                    (p.ReadBeginTime == null && p.ReadEndTime == null) ||
                    (p.ReadBeginTime == null && p.ReadEndTime != null && ((beginTime != null && p.ReadEndTime >= beginTime) || endTime != null)) ||
                    (p.ReadBeginTime != null && p.ReadEndTime == null && ((endTime != null && p.ReadBeginTime <= endTime) || beginTime != null)) ||
                    (p.ReadBeginTime != null && p.ReadEndTime != null && ((beginTime != null && endTime != null && p.ReadBeginTime <= endTime && beginTime <= p.ReadEndTime) || (beginTime != null && endTime == null && p.ReadEndTime >= beginTime) || (beginTime == null && endTime != null && p.ReadBeginTime <= endTime))))
                       .Select(t => t.Code).ToList();
        }

        private List<string> DoGetReadContextCodeByEntityName(DateTime? beginTime, DateTime? endTime, string entityName)
        {
            List<string> ret = null;
            var d = EntityConfigManager.EntityConfigs.FirstOrDefault(t => string.Compare(t.Name, entityName, true) == 0);
            if (d == null || d.ContextType == EntityConfigContextTypeEnum.TimeContext)
            {
                if (this.ContextPoolParam.Contexts != null)
                {
                    ret = this.GetReadContextCodeByList(this.ContextPoolParam.Contexts.Where(t => t.ContextType == ContextTypeEnum.TimeContext).ToList(), beginTime, endTime);
                }
            }
            else if (d.ContextType == EntityConfigContextTypeEnum.AllContext)
            {
                if (this.ContextPoolParam.Contexts != null)
                {
                    ret = this.GetReadContextCodeByList(
                        this.ContextPoolParam.Contexts.Where(t => t.ContextType == ContextTypeEnum.DefaultContext || t.ContextType != ContextTypeEnum.TimeContext).ToList(),
                        beginTime, 
                        endTime);
                }
            }
            else if (d.ContextType == EntityConfigContextTypeEnum.Default)
            {
                ret = this.ContextPoolParam.Contexts.Where(t => t.ContextType == ContextTypeEnum.DefaultContext).Select(t => t.Code).ToList();
            }
            else if (d.ContextType == EntityConfigContextTypeEnum.SpecialContext)
            {
                if (d.AllowContexts == null || !d.AllowContexts.Any())
                {
                    CommonEFUtils.ThrowBySpecialContextByEntity(this.Provider, entityName);
                }

                ret = this.GetReadContextCodeByList(
                    this.ContextPoolParam.Contexts.Where(t => t.ContextType != ContextTypeEnum.TimeContext && d.AllowContexts.Any(t1 => string.Compare(t1, t.Code, true) == 0)).ToList(),
                    beginTime,
                    endTime);
            }

            if (ret == null || !ret.Any())
            {
                CommonEFUtils.ThrowByNoContextByEntity(this.Provider, entityName);
            }

            return ret;
        }

        private List<string> GetContextCodeByList(List<CommonContextParam> list, DateTime? beginTime, DateTime? endTime)
        {
            if (list == null || !list.Any())
            {
                return new List<string>();
            }

            if (beginTime == null && endTime == null)
            {
                beginTime = DateTime.Now.Date;
                endTime = DateTime.Now.Date.AddDays(1).AddSeconds(-1);
            }

            return list.Where(p =>
                    p.ContextType == ContextTypeEnum.DefaultContext ||
                    (p.BeginTime == null && p.EndTime == null) ||
                    (p.BeginTime == null && p.EndTime != null && ((beginTime != null && p.EndTime >= beginTime) || endTime != null)) ||
                    (p.BeginTime != null && p.EndTime == null && ((endTime != null && p.BeginTime <= endTime) || beginTime != null)) ||
                    (p.BeginTime != null && p.EndTime != null && ((beginTime != null && endTime != null && p.BeginTime <= endTime && beginTime <= p.EndTime) || (beginTime != null && endTime == null && beginTime != null && p.EndTime >= beginTime) || (beginTime == null && endTime != null && endTime != null && p.BeginTime <= endTime))))
                      .OrderBy(t => t.ContextIndex).Select(t => t.Code).ToList();
        }

        private List<string> DoGetContextCodeByEntityName(string entityName)
        {
            List<string> ret = null;
            var d = EntityConfigManager.EntityConfigs.FirstOrDefault(t => string.Compare(t.Name, entityName, true) == 0);
            if (d != null && d.ContextType == EntityConfigContextTypeEnum.TimeContext)
            {
                if (this.ContextPoolParam.Contexts != null)
                {
                    ret = this.GetContextCodeByList(this.ContextPoolParam.Contexts.Where(t => t.ContextType == ContextTypeEnum.TimeContext).ToList(), null, null);
                }
            }
            else if (d == null || d.ContextType == EntityConfigContextTypeEnum.AllContext)
            {
                if (this.ContextPoolParam.Contexts != null)
                {
                    ret = this.GetContextCodeByList(
                        this.ContextPoolParam.Contexts.Where(t => t.ContextType == ContextTypeEnum.DefaultContext || t.ContextType != ContextTypeEnum.TimeContext).ToList(),
                        null,
                        null);
                    ret.Sort();
                }
            }
            else if (d.ContextType == EntityConfigContextTypeEnum.Default)
            {
                ret = this.ContextPoolParam.Contexts.Where(t => t.ContextType == ContextTypeEnum.DefaultContext).Select(t => t.Code).ToList();
            }
            else if (d.ContextType == EntityConfigContextTypeEnum.SpecialContext)
            {
                if (d.AllowContexts == null || !d.AllowContexts.Any())
                {
                    CommonEFUtils.ThrowBySpecialContextByEntity(this.Provider, entityName);
                }

                ret = this.GetContextCodeByList(
                    this.ContextPoolParam.Contexts.Where(t => t.ContextType != ContextTypeEnum.TimeContext && d.AllowContexts.Any(t1 => string.Compare(t1, t.Code, true) == 0)).ToList(), 
                    null,
                    null);
                ret.Sort();
            }

            if (ret == null || !ret.Any())
            {
                CommonEFUtils.ThrowByNoContextByEntity(this.Provider, entityName);
            }

            return ret;
        }
    }
}
